package arkham.util;

import java.awt.*;
import java.awt.event.MouseListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

import javax.imageio.ImageIO;
import javax.swing.JComponent;

public class ImageUtils {

	private static HashMap<String, Font> fontMap = new HashMap<String, Font>();
	private static HashMap<String, BufferedImage> imageMap = new HashMap<String, BufferedImage>();

	/**
	 * Loads an image from a file
	 * @param filename The filename to load from
	 * @return The image object representing that image
	 */
	public static BufferedImage loadImage(String filename){

		if (imageMap.containsKey(filename))
		{
			return imageMap.get(filename);
		}

		try
		{
			BufferedImage img = ImageIO.read(new File(filename));
			imageMap.put(filename, img);
			return img;
		}
		catch(IOException e)
		{
			AHLogger.error(e.getMessage());
			AHLogger.error("Error loading image file " + filename);
			return null;
		}
	}

	/**
	 * Resizes an image in a smooth and nice fashion
	 * @param image the image to resize
	 * @param width the new width of the image
	 * @param height the new height of the image
	 * @return the resized image
	 */
	public static BufferedImage resizeImage (BufferedImage image, int width, int height) {
		int type = image.getType() == 0? BufferedImage.TYPE_INT_ARGB : image.getType();
		BufferedImage resizedImage = new BufferedImage(width, height, type);
		Graphics2D g = resizedImage.createGraphics();
		g.setComposite(AlphaComposite.Src);

		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BILINEAR);

		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);

		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		g.drawImage(image, 0, 0, width, height, null);
		g.dispose();
		return resizedImage;
	}

	/**
	 * Creates a blank BufferedImage with the specified size and color.
	 * @param width the width of the image
	 * @param height the height of the image
	 * @param color the color of the image
	 * @return the buffered image
	 */
	public static BufferedImage createBlankImage(int width, int height, Color color)
	{
	    BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
	    Graphics2D g = img.createGraphics();
	    g.setColor(color);
	    g.fillRect(0, 0, width, height);
	    g.dispose();
	    return img;
	}

	/**
	 * Loads a TTF font from a file
	 * @param filename the file to load from
	 * @return the font
	 */
	public static Font loadFont(String filename)
	{
		if (fontMap.containsKey(filename))
		{
			return fontMap.get(filename);
		}
		Font font = null;

		try
		{
			InputStream is = new FileInputStream(new File(filename));
			font = Font.createFont(Font.TRUETYPE_FONT, is);
			is.close();
		}
		catch (Exception ex)
		{
			ex.printStackTrace();
			System.err.println(filename + " not loaded.  Using serif font.");
			font = new Font("serif", Font.PLAIN, 28);
		}
		fontMap.put(filename, font);
		return font;
	}

	/* ------------------ Utility Methods ------------------------- */
	/**
	 * Sets a component to a fixed size. Calls set minimum, maximum and
	 * preferred size for that component
	 * @param comp the component to set
	 * @param width the width
	 * @param height the height
	 */
	public static void setFixedSize(JComponent comp, int width, int height)
	{
		comp.setMinimumSize(new Dimension(width, height));
		comp.setMaximumSize(new Dimension(width, height));
		comp.setPreferredSize(new Dimension(width, height));
		comp.setSize(width, height);
	}

	public static void initGBC(GridBagConstraints gc, int fill, double weightx,
			double weighty, int gridx, int gridy)
	{
		gc.fill = fill;
		gc.weightx = weightx;
		gc.weighty = weighty;
		gc.gridx = gridx;
		gc.gridy = gridy;
	}

	/**
	 * Sets the opacity of all the child components of a component
	 * @param comp the container to set
	 * @param opaque true to set children as opaque, false to set transparent
	 */
	public static void setChildOpacity(JComponent comp, boolean opaque)
	{
		for (Component child : comp.getComponents())
		{
			if (child instanceof JComponent)
			{
				JComponent jchild = (JComponent)child;
				jchild.setOpaque(opaque);
				setChildOpacity(jchild, opaque);
			}
		}
	}

	/**
	 * Adds a mouse listener to all the children of the component
	 * @param comp the component
	 * @param ml the mouse listener to add
	 */
	public static void recursiveAddMouseListener(JComponent comp, MouseListener ml)
	{
		for (Component child : comp.getComponents())
		{
			child.addMouseListener(ml);
			if (child instanceof JComponent)
			{
				JComponent jchild = (JComponent)child;
				recursiveAddMouseListener(jchild, ml);
			}
		}
	}

	public static Color[] COLOR_POOL = new Color[] {
		Color.white, Color.yellow, new Color(168, 255, 29), Color.cyan,
		Color.orange, Color.black, new Color(0, 0, 64)
	};

	/**
	 * Gets the color most contrasting with the current color
	 * @param r red component, 0-255
	 * @param g green component, 0-255
	 * @param b blue component, 0-255
	 * @return the most contrasting color
	 */
	public static Color getContrastingColor(int r, int g, int b)
	{
		Color best = Color.black;
		float bestScore = 0;
		for (Color sample : COLOR_POOL)
		{
			int sr = sample.getRed();
			int sg = sample.getGreen();
			int sb = sample.getBlue();
			float contrast = Math.abs(r - sr) + Math.abs(g - sg) + Math.abs(b - sb);

			float bright1 = (sr * 299 + sg * 587 + sb * 114) / 1000;
			float bright2 = (r * 299 + g * 587 + b * 114) / 1000;
			float brightdiff = Math.abs(bright1 - bright2) * 7;

			if (brightdiff + contrast > bestScore)
			{
				bestScore = brightdiff + contrast;
				best = sample;
			}
		}
		return best;
	}

	/**
	 * Gets the color most contrasting with the current color
	 * @param col the current color
	 * @return the most contrasting color
	 */
	public static Color getContrastingColor(Color col)
	{
		return getContrastingColor(col.getRed(), col.getGreen(), col.getBlue());
	}

	public static boolean isWordCharacter(char q, boolean flag)
	{
		if (Character.isLetterOrDigit(q) || q == '\'')
		{
			return true;
		}
		if (!flag)
		{
			if (q == '-' || q == '_')
			{
				return true;
			}
		}

		return false;
	}

	/**
	 * Split a string into words
	 * @param s the string to split
	 * @param splitDashes set to true if dashes are to be considered a word
	 * boundary, false if dashes are to be considered a word character
	 * @return the split string
	 */
	public static String[] splitWords(String s, boolean splitDashes)
	{

		ArrayList<String> list = new ArrayList<String>();
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < s.length() - 1; i++)
		{
			char q = s.charAt(i);
			char r = s.charAt(i + 1);
			if (isWordCharacter(q, splitDashes))
			{
				sb.append(q);
				if (!isWordCharacter(r, splitDashes))
				{
					sb.append(r);
					list.add(sb.toString());
					sb.delete(0, sb.length());
					i++;
				}
			}
		}
		char p = s.charAt(s.length() - 1);
		if (isWordCharacter(p, splitDashes))
		{
			sb.append(s.charAt(s.length() - 1));
		}
		if (sb.length() > 0)
		{
			list.add(sb.toString());
		}
		return list.toArray(new String[list.size()]);
	}

	/**
	 * Split a string into a preferred number of lines based on size on the screen
	 * @param s the string to split
	 * @param lines the preferred number of lines
	 * @param g the Graphics context for font rendering purposes
	 * @param f the Font used
	 * @param splitDashes set to true to split dashes, false to preserve
	 * words with dashes
	 */
	public static String[] splitString(String s, int lines, Graphics2D g, Font f, boolean splitDashes)
	{
		FontMetrics fm = g.getFontMetrics(f);
		String[] words = splitWords(s, splitDashes);
		if (words.length <= lines)
		{
			for (int i = 0; i < words.length; i++)
			{
				words[i] = words[i].trim();
			}
			return words;
		}
		double[] sizes = new double[words.length];
		for (int i = 0; i < words.length; i++)
		{
			Rectangle2D bounds = fm.getStringBounds(words[i], g);
			sizes[i] = bounds.getWidth();
		}
		double[] cumulative = new double[words.length + 1];
		cumulative[0] = 0;
		for (int i = 1; i < cumulative.length; i++)
		{
			cumulative[i] = cumulative[i - 1] + sizes[i - 1];
		}
		double[][] dp = new double[words.length][lines + 1];
		for (int i = 0; i < words.length; i++)
		{
			Arrays.fill(dp[i], 0);
		}
		double minlen = solve(dp, cumulative, 0, lines);
		// get the answer
		String[] ans = new String[lines];
		StringBuffer sb = new StringBuffer();
		double count = 0;
		int index = 0;
		for (int i = 0; i < sizes.length; i++)
		{
			count += sizes[i];
			if (count > minlen + 0.1)
			{
				ans[index++] = sb.toString().trim();
				sb.delete(0, sb.length());
				sb.append(words[i]);
				count = sizes[i];
			}
			else
			{
				sb.append(words[i]);
			}
		}
		if (index != lines)
		{
			ans[index] = sb.toString().trim();
		}
		return ans;
	}

	private static double solve(double[][] dp, double[] cl, int pos, int left)
	{
		if (dp[pos][left] != 0)
		{
			return dp[pos][left];
		}
		if (left == 1)
		{
			dp[pos][1] = cl[cl.length - 1] - cl[pos];
			return dp[pos][1];
		}
		double bestLen = 99999;
		for (int i = pos + 1; i < cl.length - 1; i++)
		{
			double div = solve(dp, cl, i, left - 1);
			double self = cl[i] - cl[pos];
			double m = Math.max(div, self);
			if (m < bestLen)
			{
				bestLen = m;
			}
		}
		dp[pos][left] = bestLen;
		return bestLen;
	}

	/**
	 * Calculates the font size required for the text to fit in the given width,
	 * given the current font
	 * @param comp the component to render in
	 * @param f the font to be used
	 * @param width the width to fit the text into
	 * @param text the text to fit
	 * @return the largest font size that will fit
	 */
	public static float calculateFontSize(Component comp, Font f, int width, String text)
	{
		float curSize = f.getSize2D();
		FontMetrics fm = comp.getFontMetrics(f);
		double textWidth = fm.stringWidth(text);
		return (float)(width * curSize / textWidth);
	}
}